package com.anlogic.sdk.utils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.logging.log4j.Logger;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.envvar.IContributedEnvironment;
import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.core.ITool;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import com.anlogic.sdk.utils.options.ArchType;
import com.anlogic.sdk.utils.options.ProjectType;

public class ProjectUtils {
	private static Logger logger = LogUtils.getLogger(ProjectUtils.class);
	
	public static final String C_NATURE_ID = "org.eclipse.cdt.core.cnature";
	public static final String CC_NATURE_ID = "org.eclipse.cdt.core.ccnature";
	
	public static final String PLATFORM_PROJECT_NATURE = "com.anlogic.sdk.bsp.platformProject";
	public static final String APPLICATION_PROJECT_NATURE = "com.anlogic.sdk.app.applicationProject";
	
	public static final String PLATFORM_PROJECT_PROP_FILE = ".PlatformProject";
	public static final String APPLICATION_PROJECT_PROP_FILE = ".ApplicationProject";
	
	public static final String PROJECT_SETTINGS_FILE = ".settings/com.anlogic.sdk.project.settings.xml";
	public static final String PROJECT_SETTINGS_STRING_ATTR = "stringAttribute";
	public static final String PROJECT_SETTINGS_ATTR_PROJECTTYPE = "com.anlogic.sdk.project.settings.projectType";
	public static final String PROJECT_SETTINGS_ATTR_ARCHITECTURE = "com.anlogic.sdk.project.settings.architecture";
	public static final String PROJECT_SETTINGS_ATTR_OS = "com.anlogic.sdk.project.settings.os";
	public static final String PROJECT_SETTINGS_ATTR_IDEVERSION = "com.anlogic.sdk.project.settings.ideversion";
	
	public static final String RISCV_CCOMPILER="GNU RISC-V Cross C Compiler";
	public static final String ARM_CCOMPILER="GNU Arm Cross C Compiler";
	
	public static IProject[] getWorkspaceProjects() {
		IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
		return projects;
	}
	
	public static IProject getProject(String projName) {
		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projName);
		if (project == null || !project.isAccessible()) {
			return null;
		}
		return project;
	}
	
	public static IProject getProject(String projName, String projNature) {
		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projName);
		if (project == null || !project.isAccessible()) {
			return null;
		}
		
		try {
			if (!project.hasNature(projNature)) {
				return null;
			}
		} catch (CoreException e) {
			logger.error("Unable to get nature from project {}", project.getName());
			return null;
		}
		
		return project;
	}
	
	public static IProject getProjectByLaunchConfiguration(ILaunchConfiguration configuration) {
		IProject project = null;
		try {
			String projName = configuration.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_NAME, (String) null);
			if (projName == null) {
				return null;
			}
			projName = projName.trim();
			if (projName.isEmpty()) {
				return null;
			}
			project = ResourcesPlugin.getWorkspace().getRoot().getProject(projName);
			if (project == null || !project.isAccessible()) {
				return null;
			}
		} catch (CoreException e) {
			return null;
		}
		return project;
	}
	
	public static IProject getSelectedProject() {
		IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
		if (window != null) {
			Object selection = window.getSelectionService().getSelection();
			if ((selection != null) && (selection instanceof IStructuredSelection)
					&& (((IStructuredSelection) selection).size() == 1)) {
				Object firstElement = ((IStructuredSelection) selection).getFirstElement();
				if (firstElement instanceof IAdaptable) {
					IProject project = (IProject) ((IAdaptable) firstElement).getAdapter(IProject.class);
					return project;
				}
			}
		}
		return null;
	}
	
	public static List<IProject> getSelectedProjects() {
		List<IProject> projects = new ArrayList<IProject>();
		IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
		if (window != null) {
			Object selection = window.getSelectionService().getSelection();
			if ((selection != null) && (selection instanceof IStructuredSelection)) {
				IStructuredSelection structuredSelection = ((IStructuredSelection) selection);
				for (Object sel : structuredSelection.toArray()) {
					if (sel instanceof IAdaptable) {
						IProject project = (IProject) ((IAdaptable) sel).getAdapter(IProject.class);
						projects.add(project);
					}
				}
			}
		}
		return projects;
	}
	
	public static Properties getProperties(IProject project, String propertyFile) {
		Properties p = new Properties();
		File f = new File(project.getFile(propertyFile).getLocation().toOSString());
		if (!f.exists())
			return p;
		try {
			BufferedReader reader = new BufferedReader(new FileReader(f));
			try {
				while (true) {
					String line = reader.readLine();
					if (line == null)
						break;
					String[] nv = line.split("=");
					if (nv.length >= 2)
						p.setProperty(nv[0], nv[1]);
				}
			} finally {
				reader.close();
			}
		} catch (IOException e) {
			logger.error("Unable to load properties file {} for project {}", propertyFile, project.getName());
		}
		return p;
	}
	
	public static void saveProperties(IProject project, String backingFileName, Properties p) {
		File f = new File(project.getFile(backingFileName).getLocation().toOSString());
		try {
			BufferedWriter writer = new BufferedWriter(new FileWriter(f.getAbsolutePath()));
			try {
				for (Map.Entry<Object, Object> entry : p.entrySet()) {
					Object k = entry.getKey();
					Object v = entry.getValue();
					writer.write(k + "=" + v + "\n");
				}
			} finally {
				writer.close();
			}
		} catch (IOException e) {
			logger.error("Unable to save properties for Project {}", project.getName());
		}
	}
	
	public static boolean isCProject(IProject project) {
		try {
			if (project.hasNature(C_NATURE_ID))
				return true;
		} catch (CoreException e) {
			logger.error("Unable to get nature from project {}", project.getName());
			return false;
		}
		return false;
	}
	
	public static boolean isCCProject(IProject project) {
		try {
			if (project.hasNature(CC_NATURE_ID))
				return true;
		} catch (CoreException e) {
			logger.error("Unable to get nature from project {}", project.getName());
			return false;
		}
		return false;
	}
	
	public static boolean isPlatformProject(IProject project) {
		try {
			if (project.hasNature(PLATFORM_PROJECT_NATURE))
				return true;
		} catch (CoreException e) {
			logger.error("Unable to get nature from project {}", project.getName());
			return false;
		}
		return false;
	}
	
	public static boolean isApplicationProject(IProject project) {
		try {
			if (project.hasNature(APPLICATION_PROJECT_NATURE))
				return true;
		} catch (CoreException e) {
			logger.error("Unable to get nature from project {}", project.getName());
			return false;
		}
		return false;
	}
	
	public static boolean isAArch32(IProject project) {
		if (!isApplicationProject(project)) {
			return false;
		}
		
		String arch = getProjectArch(project);
		
		if (arch != null && arch.equals(ArchType.AArch32.getName())) {
			return true;
		}
		
		return false;
	}
	
	public static String getProjectVersion(IProject project) {
		File settingsFile = new File(project.getFile(PROJECT_SETTINGS_FILE).getLocation().toOSString());
		String ret = null;
		try {
			Document doc = XmlUtils.parseFile(settingsFile);
			Node node = XmlUtils.getNode(doc, String.format("/projectSettings/stringAttribute[@name=\"%s\"]/@value", PROJECT_SETTINGS_ATTR_IDEVERSION));
			if (null == node) {
				return null;
			}
			ret = node.getNodeValue();
		} catch (Exception e) {
			return null;
		}
		return ret;
	}
	
	public static String getProjectOs(IProject project) {
		File settingsFile = new File(project.getFile(PROJECT_SETTINGS_FILE).getLocation().toOSString());
		String ret = null;
		try {
			Document doc = XmlUtils.parseFile(settingsFile);
			Node node = XmlUtils.getNode(doc, String.format("/projectSettings/stringAttribute[@name=\"%s\"]/@value", PROJECT_SETTINGS_ATTR_OS));
			if (null == node) {
				return null;
			}
			ret = node.getNodeValue();
		} catch (Exception e) {
			return null;
		}
		return ret;
	}
	
	public static String getProjectArch(IProject project) {
		File settingsFile = new File(project.getFile(PROJECT_SETTINGS_FILE).getLocation().toOSString());
		String ret = null;
		try {
			Document doc = XmlUtils.parseFile(settingsFile);
			Node node = XmlUtils.getNode(doc, String.format("/projectSettings/stringAttribute[@name=\"%s\"]/@value", PROJECT_SETTINGS_ATTR_ARCHITECTURE));
			if (null == node) {
				return null;
			}
			ret = node.getNodeValue();
		} catch (Exception e) {
			return null;
		}
		return ret;
	}
	
	public static String getProjectType(IProject project) {
		File settingsFile = new File(project.getFile(PROJECT_SETTINGS_FILE).getLocation().toOSString());
		String ret = null;
		try {
			Document doc = XmlUtils.parseFile(settingsFile);
			Node node = XmlUtils.getNode(doc, String.format("/projectSettings/stringAttribute[@name=\"%s\"]/@value", PROJECT_SETTINGS_ATTR_PROJECTTYPE));
			if (null == node) {
				return null;
			}
			ret = node.getNodeValue();
		} catch (Exception e) {
			return null;
		}
		return ret;
	}
	
	public static boolean isValidProject(IProject project) {
		String projType = getProjectType(project);
		return ProjectType.isValid(projType);
	}
	
	public static IProject getBspProjectFromApp(IProject appProj) {
		if (appProj == null)
			return null;
		
		try {
			if (!appProj.hasNature(APPLICATION_PROJECT_NATURE)) {
				return null;
			}
		} catch (CoreException e) {
			return null;
		}
		
		Properties p = getProperties(appProj, APPLICATION_PROJECT_PROP_FILE);
		
		if (p.isEmpty())
			return null;
		
		IProject bspProj = getProject(p.getProperty("BSP_NAME"), PLATFORM_PROJECT_NATURE);
		
		if (bspProj == null)
			return null;
		
		return bspProj;
	}
	
	public static String getBspLocFromApp(IProject appProj) {
		if (appProj == null)
			return null;
		
		try {
			if (!appProj.hasNature(APPLICATION_PROJECT_NATURE)) {
				return null;
			}
		} catch (CoreException e) {
			return null;
		}
		
		Properties p = getProperties(appProj, APPLICATION_PROJECT_PROP_FILE);
		
		if (p.isEmpty())
			return null;
		
		IProject bspProj = getProject(p.getProperty("BSP_NAME"), PLATFORM_PROJECT_NATURE);
		
		if (bspProj == null)
			return null;
		
		String bspLoc = bspProj.getLocation().toString();
		
		return bspLoc;
	}

	public static String getHpf(IProject proj) {
		IProject bspProj = null;
		
		try {
			if (proj.hasNature(APPLICATION_PROJECT_NATURE)) {
				Properties p = getProperties(proj, APPLICATION_PROJECT_PROP_FILE);
				if (p.isEmpty())
					return null;
				bspProj = getProject(p.getProperty("BSP_NAME"), PLATFORM_PROJECT_NATURE);
			} else if (proj.hasNature(PLATFORM_PROJECT_NATURE)) {
				bspProj = proj;
			} else {
				return null;
			}
		} catch (CoreException e) {
			return null;
		}
		
		if (bspProj == null)
			return null;
		
		Properties p = getProperties(bspProj, PLATFORM_PROJECT_PROP_FILE);
		
		if (p.isEmpty())
			return null;
		
		String ret = p.getProperty("HPF_FILE");
		
		return ret;
	}
	
	public static String getChipType(IProject proj) {
		IProject bspProj = null;
		
		try {
			if (proj.hasNature(APPLICATION_PROJECT_NATURE)) {
				Properties p = getProperties(proj, APPLICATION_PROJECT_PROP_FILE);
				if (p.isEmpty())
					return null;
				bspProj = getProject(p.getProperty("BSP_NAME"), PLATFORM_PROJECT_NATURE);
			} else if (proj.hasNature(PLATFORM_PROJECT_NATURE)) {
				bspProj = proj;
			} else {
				return null;
			}
		} catch (CoreException e) {
			return null;
		}
		
		if (bspProj == null)
			return null;
		
		Properties p = getProperties(bspProj, PLATFORM_PROJECT_PROP_FILE);
		
		if (p.isEmpty())
			return null;
		
		String ret = p.getProperty("CHIP");
		
		return ret;
	}
	
	public static String getProcType(IProject proj) {
		if (proj == null)
			return null;
		
		IProject bspProj = null;
		
		try {
			if (proj.hasNature(APPLICATION_PROJECT_NATURE)) {
				Properties p = getProperties(proj, APPLICATION_PROJECT_PROP_FILE);
				if (p.isEmpty())
					return null;
				bspProj = getProject(p.getProperty("BSP_NAME"), PLATFORM_PROJECT_NATURE);
			} else if (proj.hasNature(PLATFORM_PROJECT_NATURE)) {
				bspProj = proj;
			} else {
				return null;
			}
		} catch (CoreException e) {
			return null;
		}
		
		if (bspProj == null)
			return null;
		
		Properties p = getProperties(bspProj, PLATFORM_PROJECT_PROP_FILE);
		
		if (p.isEmpty())
			return null;
		
		String ret = p.getProperty("PROCESSOR");
		
		if (ret.contains("apu")) {
			ret = "apu";
		} else if (ret.contains("rpu")) {
			ret = "rpu";
		}
		
		return ret;
	}
	
	public static boolean isManagedBuildOn(IProject project) {
		 IConfiguration[] projectConfigs = ManagedBuildManager.getBuildInfo((IResource)project).getManagedProject().getConfigurations();
		 for (IConfiguration cfg : projectConfigs) {
			 return cfg.isManagedBuildOn();
		 }
		 return false;
	}
	
	public static String getArtifactExtension(IProject project) {
		 IConfiguration[] projectConfigs = ManagedBuildManager.getBuildInfo((IResource)project).getManagedProject().getConfigurations();
		 for (IConfiguration cfg : projectConfigs) {
			 return cfg.getArtifactExtension();
		 }
		 return "";
	}
	
	public static void setBuildVariables(IProject project, Map<String, String> vars) {
		IContributedEnvironment environment = CCorePlugin.getDefault().getBuildEnvironmentManager().getContributedEnvironment();
		ICProjectDescription projectDescription = CoreModel.getDefault().getProjectDescription(project, true);
		ICConfigurationDescription[] configs = projectDescription.getConfigurations();

		// Add variable to project configuration
		for(Map.Entry<String, String> entry : vars.entrySet()) {
			for (ICConfigurationDescription config : configs) {
				environment.addVariable(entry.getKey(), entry.getValue(), IEnvironmentVariable.ENVVAR_APPEND, null, config);
			}
		}
		
		try {
			CoreModel.getDefault().setProjectDescription(project, projectDescription);
		} catch (CoreException e) {
			logger.error("Unable to add variable to project configuration {}", project.getName());
		}
	}
	
	public static String getToolchainArch(IProject project) {
		IManagedBuildInfo buildInfo = ManagedBuildManager.getBuildInfo(project);
        IConfiguration config = buildInfo.getDefaultConfiguration();
        if (config == null) {
        	config = buildInfo.getManagedProject().getConfigurations()[0];
        }
        String toolchainName = config.getToolChain().getName();
        if (toolchainName.toUpperCase().contains("ARM")) {
        	return "ARM";
		}
        if (toolchainName.toUpperCase().contains("RISCV")) {
        	return "RISCV";
		}
		return "";
	}
	
	public static void setHiddenCCompiler(IProject project) {
		IConfiguration[] projectConfigs = ManagedBuildManager.getBuildInfo((IResource) project).getManagedProject()
				.getConfigurations();
		if (isCCProject(project)) {
			for (int i = 0; i < projectConfigs.length; i++) {
				IConfiguration config = projectConfigs[i];
				ITool[] tools = config.getTools();
				for (ITool iTool : tools) {
					String name = iTool.getName();
					if (RISCV_CCOMPILER.equals(name)||ARM_CCOMPILER.equals(name)) {
						iTool.setHidden(true);
					}
				}
			}
		}
	}
	
	public static void addReferencedProject(IProject project, IProject reference) {
		try {
			IProjectDescription descripton = project.getDescription();
			List<IProject> referencedProjects = new ArrayList<>();
			referencedProjects.addAll(Arrays.asList(descripton.getReferencedProjects()));
			referencedProjects.add(reference);
			descripton.setReferencedProjects(referencedProjects.<IProject>toArray(new IProject[0]));
			if (project.exists() && project.isAccessible())
				project.setDescription(descripton, null); 
		} catch (CoreException e) {
			if (project.exists()) {
		        logger.error("Failed to update project references for project {}", project.getName());
			}
		}
	}
	
	public static IProject[] getReferencedProjects(IProject project) {
		try {
			IProjectDescription descripton = project.getDescription();
			return descripton.getReferencedProjects();
		} catch (CoreException e) {
			if (project.exists()) {
				logger.error("Failed to get project references for project {}", project.getName());
			}
		}
		return null;
	}
	
	public static IProject[] getReferencedThisProjects(IProject project) {
		List<IProject> referencedProjects = new ArrayList<>();
		try {
			for (IProject proj : getWorkspaceProjects()) {
				IProjectDescription descripton = proj.getDescription();
				for (IProject ref : descripton.getReferencedProjects()) {
					if (ref.equals(project)) {
						referencedProjects.add(proj);
					}
				}
			}
		} catch (CoreException e) {
			logger.error("Failed to get project references for project {}", project.getName());
		}
		return referencedProjects.toArray(new IProject[0]);
	}
	
	public static void refreshLocal(IProject project, int depth, IProgressMonitor monitor) {
		try {
			project.refreshLocal(depth, monitor);
		} catch (CoreException e) {
	        logger.warn("Failed to get refresh project {}", project.getName());
		}
	}
	
	public static void cleanProject(IProject project) {
		try {
			project.build(IncrementalProjectBuilder.CLEAN_BUILD, null);
			project.refreshLocal(2, null);
		} catch (CoreException e) {
			logger.info("Clean and refresh local failed when reset project!");
		}
	}
	
	public static String getUartDevice(IProject proj) {
		IProject bspProj = null;
		
		try {
			if (proj.hasNature(APPLICATION_PROJECT_NATURE)) {
				Properties p = getProperties(proj, APPLICATION_PROJECT_PROP_FILE);
				if (p.isEmpty())
					return null;
				bspProj = getProject(p.getProperty("BSP_NAME"), PLATFORM_PROJECT_NATURE);
			} else if (proj.hasNature(PLATFORM_PROJECT_NATURE)) {
				bspProj = proj;
			} else {
				return null;
			}
		} catch (CoreException e) {
			return null;
		}
		
		if (bspProj == null) {
			return null;
		}
		
		String filePath = bspProj.getLocation().toString() + "/tools/make/config.mk";
		
		File file = new File(filePath);
		
		if (!file.exists()) {
			return null;
		}
		
		String content = FileUtils.read(file);
		
        Pattern pattern = Pattern.compile("OUTPUT_DEV\\s*\\?=\\s*(\\w+)");
        Matcher matcher = pattern.matcher(content);
        
        if (matcher.find()) {
        	String uart = matcher.group(1);
        	return uart;
        }
		
		return null;
	}
	
	public static boolean isDebugging(IProject project) {
		if (project == null) {
			return false;
		}
		
	    ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
	    for (ILaunch launch : launchManager.getLaunches()) {
	        if (launch.isTerminated()) {
	            continue;
	        }
	        
	        String mode = launch.getLaunchMode();
	        if (!ILaunchManager.DEBUG_MODE.equals(mode) && !ILaunchManager.RUN_MODE.equals(mode)) {
	            continue;
	        }
	        
	        ILaunchConfiguration config = launch.getLaunchConfiguration();
	        if (config == null) {
	            continue;
	        }
	        
			try {
				IResource[] mappedResources = config.getMappedResources();
	            if (mappedResources != null) {
	                for (IResource resource : mappedResources) {
	                    IProject proj = resource.getProject();
	                    if (proj != null && proj.equals(project)) {
	                        return true;
	                    }
	                }
	            }
			} catch (CoreException e) {
				continue;
			}
	    }
	    return false;
	}
}
